/*
 *  HoneywellUDP.c
 *  SmartHome
 *
 *  Created by Tiger.Yin on 10-4-14.
 *  Copyright 2010 Beijing Oriental Caesar Co.,Ltd. All rights reserved.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/select.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include <dirent.h>
#include <pthread.h>
#include <netdb.h>
#include <android/log.h>


#define	_IN_HONEYWELL_UDP
#include "HoneywellUDP.h"
#include "HoneywellXML.h"
#include "MD5.h"

#define	TCHAR char

char reply[MAX_UDP_REP_LEN];
char token[MAX_TOKEN_LEN];
int is_verified;	// 是否已经verify过？

extern void trim_string(char *str);
extern void debug_printf(const char *, ...);

#ifndef	_TIGER_TRACE
#define	PUTLOG	debug_printf
#else
extern void PUTLOG(TCHAR *, ...);
#endif

char hris1000_ip[32];
char gUserName[64];
char gPassword[64];
char UDID[128];
char authString[64];

int auth_flag;

void auto_verify();

#if	1
int sock;
struct sockaddr_in server;

int init_UDP_sock(char *server_ip, unsigned port)
{
	char svr_ip[64];
	
	//strcpy(gUserName, "admin");
	//strcpy(gPassword, "admin");
	
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock == -1) return -1;
	
	if(server_ip == NULL || strlen(server_ip) == 0)
		strcpy(svr_ip, "168.160.192.26");	// Default value.
	else strcpy(svr_ip, server_ip);
	
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.s_addr = inet_addr(svr_ip);

	is_verified = 0;	// 还没verify过
	__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "UDP sock=%d", sock);
	return sock;
}

void close_UDP_sock()
{
	if(sock != -1)
		close(sock);
	sock = -1;
}

int send_UDP_data(char *data, int len)
{
	int ret;
	
	if(sock == -1) return -1;
	ret = sendto(sock, (const char *)data, len, 0,
				 (struct sockaddr *)&server, sizeof(struct sockaddr));
	if(ret < 0)
		return -1;
	if(ret != len)
	{
		PUTLOG("Send UDP data in-completly, ret=%d <> len=%d\n", ret, len);
		return -1;
	}
	return ret;
}

int recv_UDP_reply()
{
	int ret, saddr_len;
	struct sockaddr serveraddr;
	
	if(sock == -1) return -1;
	
	ret = recvfrom(sock, reply, MAX_UDP_REP_LEN, 0,
				   &serveraddr, (socklen_t*)&saddr_len);
	if(ret <= 0)
	{
		PUTLOG("Receive UDP reply failed!\n");
		return -1;
	}
	return ret;
}

int send_UDP_data_with_reply(char *srcdata, int len)
{
	int ret, i;
	if(sock == -1) return -1;
	
	if(auth_flag == 0) return -1;
	
#if 0
	connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr));
	
	struct timeval delay = {10, 0}; // 10 seconds timeout.
	fd_set fd_write;
connect_udp_redo:
	FD_ZERO(&fd_write);
	FD_SET(sock, &fd_write);
	switch(select(sock+1, NULL, &fd_write, NULL, &delay))
	{
        case    -1:
			PUTLOG("Error: select failed! errno=%d\n", errno);
			if(errno == EINTR)
				goto connect_udp_redo;
			return -1;
			break;
        case    0:      // Time out!
			PUTLOG("Time out while connecting to server!\n");
			return -1;
			break;
        default:
			if(FD_ISSET(sock, &fd_write))
			{
				int err;
				unsigned int len;
				getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len);
				if(err != 0)
				{
					PUTLOG("Connect Infinity FAILED! err=%d\n", err);
					return -1;
				}
				else PUTLOG("Connect Infinity OK!\n");
			}
			break;
	}
#endif
	
	int flags = fcntl(sock, F_GETFL);
	fcntl(sock, F_SETFL, flags | O_NDELAY);     // set non-block mode.
		
	ret = sendto(sock, (const char *)srcdata, len, 0,
				 (struct sockaddr *)&server, sizeof(struct sockaddr));
	//ret = send(sock, (const char *)srcdata, len, 0);
	if(ret < 0)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "errno: %d\n", errno);
		return -1;
	}
	if(ret != len)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "Send UDP data failed, ret=%d <> len=%d\n", ret, len);
		return -1;
	}
	if(srcdata[0] == '$')
		__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "Send >>> %s\n", srcdata);
	else	__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "Send >>> %s\n", srcdata+strlen(token));
	
	// waiting for reply
	memset(reply, 0, MAX_UDP_REP_LEN);
	for(i=0; i<DEFAULT_UDP_DELAY_NUM; i++)
	{
		ret = recv(sock, (char *)reply, MAX_UDP_REP_LEN, 0);
		if(ret <= 0)
		{
			__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "recv nothing...\n");
			usleep(DEFAULT_UDP_DELAY*1000);
			continue;
		}
		else break;
	}
	if(strlen(reply) > 0)
	{
		if(reply[0] == '$')
			__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "Recv <<< %s\n", reply);
		else	
			__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "Recv <<< %s\n", reply+strlen(token));
	}
	
	return ret;
}
#endif

int get_reply_error(char *rep)
{
	int ret = -1, i;
	char *ptr;
	
	// find the last comma
	ptr = rep + strlen(rep) - 1;
	for(i=0; i<6; i++)
	{
		if(*ptr == ',')
		{
			ret = atoi(ptr+1);
			break;
		}
		ptr--;
	}
	return ret;
}

// 统一使用此函数判断回复格式的有效性，回复是否以token开头都可以。
// 协议上虽然定义回复必须以token开头，但实际上发现有的回复没有以token开头。
// 今后应加入对verify103/4/5的自动处理。
// Tiger: 2010-1-5: 为了解决Infinity重启后，Token失效的问题，应在Infinity
//		reply "$verify,error,103"的时候重新认证，因此在此处加入is_verified=0.
// return 1 on success, or 0.
int is_good_reply(char *cmd)
{
	//	auto_verify();	// Tiger: 每次成功的UDP交互后都判断token是否还有效。
	
	if(strncmp(reply, token, strlen(token)) != 0)
	{ // No token reply!
		if(strncmp(reply, cmd, strlen(cmd)) != 0)
		{
			is_verified = 0;
			return 0;
		}
	}
	else
	{
		if(strncmp(reply+strlen(token), cmd, strlen(cmd)) != 0)
		{
			is_verified = 0;
			return 0;
		}
	}
	return 1;
}

//////////////////////////////////////////////////////////////////////////
// Commands of Honeywell protocol.
//////////////////////////////////////////////////////////////////////////

// return -1 on error, 0 on bad reply, otherwise the reply length.
int send_cmd_verify(char *username, char *password)
{
	int ret;
	unsigned char md5str[48];
	wchar_t tmpbuf[64];
	
	if(username == NULL || password == NULL)
		return -1;
	ret = strlen(username);
	if(ret == 0 || ret > 64) return -1;
	ret = strlen(password);
	if(ret == 0 || ret > 64) return -1;
	
	sprintf(reply, "$verify,%s,", username);
	
	if(1)
	{
		char tmpstr[3];
		// 这里，password必须先转换成UCS2编码才行，然后作MD5。

		int i;
		for(i=0; i<strlen(password); i++)
		{
			((unsigned char *)tmpbuf)[i*2+1] = 0x00;
			((unsigned char *)tmpbuf)[i*2] = (unsigned char)password[i];
		}
		
		vMD5Crypt((unsigned char *)tmpbuf, strlen(password)*2, md5str);
		
		for(i=0; i<16; i++)
		{
			sprintf(tmpstr, "%02x", md5str[i]);
			strcat(reply, tmpstr);
		}
		strcat(reply, "\n");
		__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "password=%s", reply);
	}
	else
		strcat(reply, "19a2854144b63a8f7617a6f225019b12\n");
	
	ret = send_UDP_data_with_reply(reply, strlen(reply));
	if(ret != -1)
	{
		if(strlen(reply) < strlen("$verify,"))
			return -2;
		if(strncmp(reply, "$verify,", 8) != 0)	// not the verify reply
			return -3;
		if(strncmp(reply+8, "error", 5) == 0)	// Verify error
			return -4;
		
		strcpy(token, reply + 8);
		trim_string(token);
		is_verified = 1;
	}
	
	return ret;
}


// Tiger: 协议中token的有效期为20分钟，我们在程序中实现如果超过10分钟没有操作，则自动进行
//		verify动作。auto_verify()实现此功能，应在每个发送指令的代码中加入之。
void auto_verify()
{
	static time_t tt=0;
	time_t cur_tt;
	
	time(&cur_tt);
	
	if((cur_tt - tt) >= DEFAULT_VERIFY_DELAY)	// time out
	{
		is_verified = 0;
		__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "AUTO VERIFYING...\n");
		send_cmd_verify(gUserName, gPassword);
		if(is_verified)
			tt = cur_tt;
		return;
	}
	if(!is_verified)	// Not verified yet!
	{
		send_cmd_verify(gUserName, gPassword);
		if(is_verified)
			tt = cur_tt;
	}
	
	__android_log_print(ANDROID_LOG_DEBUG, "honey-TEST", "is_verified=%d, tt=%u, cur_tt=%u, cur_tt-tt=%d\n",
		is_verified, (unsigned int)tt, (unsigned int)cur_tt, (int)(cur_tt-tt));
}

// return -1 on connection or sync error, otherwise the error number of reply.
int send_cmd_light(int id, int action, int onoff, int dimmer)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;
	
	sprintf(request, "%s$cfg,lig,%d,%d,%d,%d,0\n",
			token, id, action, onoff, dimmer);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,lig,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

/*-----------------------------------------------------------------------------
 经测试发现本命令的回复并不符合协议，回复文本并不以token开头！网页上也不停弹出
 “操作未成功”的对话框。网络截包确认。
 -----------------------------------------------------------------------------*/
int send_cmd_ac485_cfg(int id, int onoff, int mode, int fan, int dir, int temp)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,ac,%d,%d,%d,%d,%d,%d,0,0\n",
			token, id, onoff, mode, fan, 255, temp);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,ac,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}
/* 如果访问不存在的485空调，则没有回复 */
int send_cmd_ac485_req(int id, int *onoff, int *mode, int *fan,
					   int *dir, int *temp_set, int *temp_cur)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret, tmpid;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$req,ac,%d,0,0,0,0,0,0,0\n",
			token, id);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$res,ac,"))
			return -1;
		if(reply[0] == '$')	// 返回的reply就没有token
			sscanf(reply+8, "%d,%d,%d,%d,%d,%d,%d,%d\n",
				   &tmpid, onoff, mode, fan, dir, temp_set, temp_cur, &ret);
		else
			sscanf(reply+strlen(token)+8, "%d,%d,%d,%d,%d,%d,%d,%d\n",
				   &tmpid, onoff, mode, fan, dir, temp_set, temp_cur, &ret);
	}
	
	return ret;
}

int send_cmd_irac(int id, int irid)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,ac,%d,%d,0\n",
			token, id, irid);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,ac,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_irdev(int id, int irid)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;
	
	sprintf(request, "%s$cfg,ir,%d,%d,0\n",
			token, id, irid);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,ir,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_ufh_cfg(int id, int onoff, int temp_set)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,ufh,%d,%d,%d,0,0\n",
			token, id, onoff, temp_set);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,ufh,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_ufh_req(int id, int *onoff, int *temp_set, int *temp_cur)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret, tmpid;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$req,ufh,%d,0,0,0,0\n",
			token, id);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$res,ufh,"))
			return -1;
		sscanf(reply+strlen(token)+9, "%d,%d,%d,%d,%d\n",
			   &tmpid, onoff, temp_set, temp_cur, &ret);
	}
	
	return ret;
}

int send_cmd_relay_cfg(int id, int onoff)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,relay,%d,%d,0,0,0,0\n",
			token, id, onoff);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,relay,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_relay_req(int id, int *onoff)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret, tmpid, d1, d2, d3;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$req,relay,%d,0,0,0,0,0\n",
			token, id);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$res,relay,"))
			return -1;
		sscanf(reply+strlen(token)+11, "%d,%d,%d,%d,%d,%d\n",
			   &tmpid, onoff, &d1, &d2, &d3, &ret);
	}
	
	return ret;
}

int send_cmd_curtain(int id, int action, int onoff)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,curtain,%d,%d,%d,0\n",
			token, id, action, onoff);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,curtain,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_zone_cfg(int zoneid, int ztype, int action)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,alarm,%d:%d,%d\n",
			token, zoneid, ztype, action);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,alarm,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_zone_req(int zoneid, int ztype, int statuskey, int *nstatuskey, char *zoneinfo)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret, tmpid, d1, d2;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$req,alarm,%d:%d:%d,0\n",
			token, zoneid, ztype, statuskey);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$res,alarm,"))
			return -1;
		ret = sscanf(reply+strlen(token)+11, "%d:%d:%d,%d,%s\n",
					 &tmpid, &d1, &d2, nstatuskey, zoneinfo);
	}
	
	return ret;
}

// system controll commands

int send_cmd_scenario_cfg(int sid)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,scenario,%d,1\n",
			token, sid);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,scenario,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_scenario_req(int areaid, int *sid)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$req,scenario,%d,1\n",
			token, areaid);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$res,scenario,"))
			return -1;
		sscanf(reply+strlen(token)+14, "%d,%d\n",
			   sid, &ret);
	}
	
	return ret;
}

int send_cmd_trigger_cfg(int id, int onoff)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$cfg,trigger,%d,%d\n",
			token, id, onoff);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$ack,trigger,"))
			return -1;
		ret = get_reply_error(reply);
	}
	
	return ret;
}

int send_cmd_trigger_req(int id, int *sid)
{
	char request[MAX_UDP_REQ_LEN + 32];
	int ret, tmpid;
	
	auto_verify();
	if(!is_verified) return -1;

	sprintf(request, "%s$req,trigger,%d,0,0\n",
			token, id);
	if(strlen(request) > MAX_UDP_REQ_LEN) return -1;
	
	ret = send_UDP_data_with_reply(request, strlen(request));
	if(ret != -1)
	{
		if(!is_good_reply("$res,trigger,"))
			return -1;
		sscanf(reply+strlen(token)+13, "%d,%d,%d\n",
			   &tmpid, &ret, sid);
	}
	
	return ret;
}


